﻿using System;
using System.Collections.Generic;
using System.Reflection;
using System.Linq;
using ProxyViewModel.Attributes;
using System.Threading;
using System.Diagnostics;
using System.Windows.Threading;

namespace ProxyViewModel.Messaging {
	internal class MessengerManager {
		internal class TypeAndMethodTuple {
			private readonly Type _type;
			private readonly MethodInfo _methodInfo;

			public TypeAndMethodTuple(Type type, MethodInfo methodInfo) {
				_type = type;
				_methodInfo = methodInfo;
			}

			public MethodInfo MethodInfo {
				get { return _methodInfo; }
			}

			public Type Type {
				get { return _type; }
			}
		}

		private static object _lock = new object();
		private static object _lock1 = new object();
		private static readonly Dictionary<Type, IList<WeakReference>> _typeAndVmMap = new Dictionary<Type, IList<WeakReference>>();
		private static readonly Dictionary<MessageContract, IList<TypeAndMethodTuple>> _messageContractAndTypeMethodMap =
			new Dictionary<MessageContract, IList<TypeAndMethodTuple>>();
		private static readonly List<Type> _analyzedTypes = new List<Type>();
		private static readonly Dictionary<MethodInfo, bool> _methodOnNewThreadMap = new Dictionary<MethodInfo, bool>();
		private static readonly string MessageMethodName = "OnViewModelMessageRecieved";

		public static void RegisterViewModel(object viewModel) {
			Type viewModelType = viewModel.GetType();
			Monitor.Enter(_lock);
			try {
				if (!_analyzedTypes.Contains(viewModelType)) {
					IDictionary<MethodInfo, MessageContract> methodAndContractMap = CollectMessagingContracts(viewModelType);
					if (methodAndContractMap.Count == 0) {
						return;
					}
					foreach (MethodInfo info in methodAndContractMap.Keys) {
						MessageContract contract = methodAndContractMap[info];
						if (!_messageContractAndTypeMethodMap.ContainsKey(contract)) {
							_messageContractAndTypeMethodMap[contract] = new List<TypeAndMethodTuple>();
						}
						_messageContractAndTypeMethodMap[contract].Add(new TypeAndMethodTuple(viewModelType, info));
					}
				}
				if (!_typeAndVmMap.ContainsKey(viewModelType)) {
					_typeAndVmMap[viewModelType] = new List<WeakReference>();
				}
				_typeAndVmMap[viewModelType].Add(new WeakReference(viewModel));
				_analyzedTypes.Add(viewModelType);
			}
			finally {
				Monitor.Exit(_lock);
			}
		}

		private static IDictionary<MethodInfo, MessageContract> CollectMessagingContracts(Type viewModelSourceType) {
			IDictionary<MethodInfo, MessageContract> result = new Dictionary<MethodInfo, MessageContract>();

			List<MethodInfo> applicableMethods = viewModelSourceType.GetMethods(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public).
				Where(
					e => (e.GetCustomAttributes(true).OfType<MessageRecieverAttribute>().Count() > 0 || e.Name.Equals(MessageMethodName))
						 && e.ReturnType == typeof(void)
						 && e.GetParameters().Length > 0
					).ToList();

			applicableMethods.ForEach(e => {
				result.Add(e,
				new MessageContract(
					e.GetParameters().Select(p => p.ParameterType).ToArray(),
					e.GetCustomAttributes(true).OfType<MessageRecieverAttribute>().Count() == 1 ?
						e.GetCustomAttributes(true).OfType<MessageRecieverAttribute>().First().Selector : string.Empty));
			});

			return result;
		}

		public static void PublishMessage(string selector, params object[] messageParameters) {
			MessageContract inputContract = new MessageContract(messageParameters.Select(e => e.GetType()).ToArray(), selector);

			if (!_messageContractAndTypeMethodMap.ContainsKey(inputContract)) {
				return;
			}
			foreach (TypeAndMethodTuple tuple in _messageContractAndTypeMethodMap[inputContract]) {
				foreach (WeakReference viewModelReference in _typeAndVmMap[tuple.Type]) {
					if (viewModelReference.IsAlive && viewModelReference.Target != null) {
						object viewModel = viewModelReference.Target;
						ThreadPool.QueueUserWorkItem(e => {
							if (CallOnNewThread(tuple.MethodInfo) || !(viewModel is IDispatcherContainer)) {
								tuple.MethodInfo.Invoke(viewModel, messageParameters);
							}
							else {
#if !SILVERLIGHT
								((IDispatcherContainer)viewModel).Dispatcher.Invoke(new Action(() => {
									tuple.MethodInfo.Invoke(viewModel, messageParameters);
								}), DispatcherPriority.Send);
#else
								((IDispatcherContainer)viewModel).Dispatcher.BeginInvoke(new Action(() => {
									tuple.MethodInfo.Invoke(viewModel, messageParameters);
								}));
#endif
							}
						});
					}
				}
			}
		}

		private static bool CallOnNewThread(MethodInfo messageReciever) {
			if (_methodOnNewThreadMap.ContainsKey(messageReciever)) {
				return _methodOnNewThreadMap[messageReciever];
			}
			lock (_lock1) {
				bool callOnNewThread = false;
				MessageRecieverAttribute attr = messageReciever.GetCustomAttributes(typeof(MessageRecieverAttribute), true).OfType<MessageRecieverAttribute>().FirstOrDefault();
				if (attr != null) {
					callOnNewThread = attr.OnNewThread;
				}
				_methodOnNewThreadMap[messageReciever] = callOnNewThread;
			}

			return _methodOnNewThreadMap[messageReciever];
		}
	}
}
